#ifndef MLTOOL_MATRIX_H
#define MLTOOL_MATRIX_H

#include<utility>
#include<cassert>
#include"vector.h"
#include"tags.h"

namespace math{
template<class T>
class matrix: public vector<T>
{
public:
    typedef vector<T> Array;
    typedef typename Array::difference_type difference_type;
    typedef typename Array::size_type size_type;
    typedef typename Array::value_type value_type;
    typedef typename Array::pointer pointer;
    typedef typename Array::const_pointer const_pointer;
    typedef typename Array::reference reference;
    typedef typename Array::const_reference const_reference;
    typedef typename Array::iterator iterator;
    typedef typename Array::const_iterator const_iterator;
    typedef typename Array::reverse_iterator reverse_iterator;
    typedef typename Array::const_reverse_iterator const_reverse_iterator;
	typedef matrix_tag category;

	using vector<T>::data_;
    template<class U>
    friend class matrix;

    matrix()
        :rows(0), columns(0)
    {}

    matrix(size_type m, size_type n)
        :rows(m), columns(n), vector<T>(m*n)
    {}

    matrix(size_type m, size_type n, const value_type& init)
        :rows(m), columns(n), vector<T>(m*n, init)
    {}
	
	//conversion
    template<class U>
    matrix(const matrix<U> &m)
        :rows(m.rows), columns(m.columns),
		vector<T>(m)
    {}

	inline value_type& operator()(size_type i, size_type j){
        return data_[i * columns + j];
    }

    inline const value_type& operator()(size_type i, size_type j) const{
        return data_[i * columns + j];
    }

    inline pointer operator[](size_type i){
        return data_ + i * columns;
    }

    inline const_pointer operator[](size_type i) const{
        return data_ + i * columns;
    }

	inline void swap(matrix &m)
    {
       std::swap(rows, m.rows);
       std::swap(columns, m.columns);
	   vector<T>::swap(m);
    }

    friend inline void swap(matrix& m1, matrix& m2)
    {
        m1.swap(m2);
    }

	inline size_type size1() const{return rows;}
    inline size_type size2() const{return columns;}


	//operator +
    friend inline matrix operator +(const matrix &m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "1st called!" << std::endl;
        matrix ans(m1.rows, m1.columns);
		for(size_type i = 0; i < m1.size(); ++i)
			ans.data_[i] = m1.data_[i] + m2.data_[i];
        return ans;
    }

    friend inline matrix&& operator +(matrix &&m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "2nd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] += m2.data_[i];
        return std::move(m1);
    }

    friend inline matrix&& operator +(const matrix &m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "3rd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m2.data_[i] += m1.data_[i];
        return std::move(m2);
    }

    friend inline matrix&& operator +(matrix &&m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "4th called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] += m2.data_[i];
        return std::move(m1);
    }

	//operator -
    friend inline matrix operator -(const matrix &m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "1st called!" << std::endl;
        matrix ans(m1.rows, m1.columns);
		for(size_type i = 0; i < m1.size(); ++i)
			ans.data_[i] = m1.data_[i] - m2.data_[i];
        return ans;
    }

    friend inline matrix&& operator -(matrix &&m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "2nd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] -= m2.data_[i];
        return std::move(m1);
    }

    friend inline matrix&& operator -(const matrix &m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "3rd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m2.data_[i] = m1.data_[i] - m2.data_[i];
        return std::move(m2);
    }

    friend inline matrix&& operator -(matrix &&m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "4th called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] -= m2.data_[i];
        return std::move(m1);
    }

	//operator *
    friend inline matrix operator *(const matrix &m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "1st called!" << std::endl;
        matrix ans(m1.rows, m1.columns);
		for(size_type i = 0; i < m1.size(); ++i)
			ans.data_[i] = m1.data_[i] * m2.data_[i];
        return ans;
    }

    friend inline matrix&& operator *(matrix &&m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "2nd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] *= m2.data_[i];
        return std::move(m1);
    }

    friend inline matrix&& operator *(const matrix &m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "3rd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m2.data_[i] *= m1.data_[i];
        return std::move(m2);
    }

    friend inline matrix&& operator *(matrix &&m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "4th called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] *= m2.data_[i];
        return std::move(m1);
    }

	//operator /
    friend inline matrix operator /(const matrix &m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "1st called!" << std::endl;
        matrix ans(m1.rows, m1.columns);
		for(size_type i = 0; i < m1.size(); ++i)
			ans.data_[i] = m1.data_[i] / m2.data_[i];
        return ans;
    }

    friend inline matrix&& operator /(matrix &&m1, const matrix &m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "2nd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] /= m2.data_[i];
        return std::move(m1);
    }

    friend inline matrix&& operator /(const matrix &m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "3rd called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m2.data_[i] = m1.data_[i] / m2.data_[i];
        return std::move(m2);
    }

    friend inline matrix&& operator /(matrix &&m1, matrix &&m2)
    {
#ifdef CHECK_DIMENSION_MATCH
       assert(m1.rows == m2.rows);
       assert(m1.columns == m2.columns);
#endif
        std::cout << "4th called!" << std::endl;
		for(size_type i = 0; i < m1.size(); ++i)
			m1.data_[i] /= m2.data_[i];
        return std::move(m1);
    }

	//operator - (负号)
	friend inline matrix operator -(const matrix &m)
	{
		matrix ans(m.size1(), m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = -m.data_[i];
		return ans;
	}

	friend inline matrix&& operator -(matrix &&m)
	{
		m *= -1;
		return std::move(m);
	}
    // 矩阵和标量的运算
    //operator +
	friend inline matrix operator +(const matrix &m, value_type t)
	{
		matrix ans(m.size1(), m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = m.data_[i] + t;
		return ans;
	}

	friend inline matrix operator +(value_type t, const matrix &m)
	{
		return m + t;
	}

	friend inline matrix&& operator +(matrix &&m, value_type t)
	{
		m += t;
		return std::move(m);
	}

	friend inline matrix&& operator +(value_type t, matrix&&m)
	{
		m += t;
		return std::move(m);
	}

    // operator -
	friend inline matrix operator -(const matrix &m, value_type t)
	{
		matrix ans(m.size1(), m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = m.data_[i] - t;
		return ans;
	}

	friend inline matrix operator -(value_type t, const matrix &m)
	{
		matrix ans(m.size1(). m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = t - m.data_[i];
		return ans;
	}

	friend inline matrix&& operator -(matrix &&m, value_type t)
	{
		m -= t;
		return std::move(m);
	}

	friend inline matrix&& operator -(value_type t, matrix&&m)
	{
		for(size_type i = 0; i < m.size(); ++i)
			m.data_[i] = t - m.data_[i];
		return std::move(m);
	}
	
	// operator *
	friend inline matrix operator *(const matrix &m, value_type t)
	{
		matrix ans(m.size1(), m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = m.data_[i] * t;
		return ans;
	}

	friend inline matrix operator *(value_type t, const matrix &m)
	{
		return m * t;
	}

	friend inline matrix&& operator *(matrix &&m, value_type t)
	{
		m *= t;
		return std::move(m);
	}


	friend inline matrix&& operator *(value_type t, matrix &&m)
	{
		m *= t;
		return std::move(m);
	}

	//operator /
	friend inline matrix operator /(const matrix &m, value_type t)
	{
		matrix ans(m.size1(), m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = m.data_[i] / t;
		return ans;
	}

	friend inline matrix operator /(value_type t, const matrix &m)
	{
		matrix ans(m.size1(). m.size2());
		for(size_type i = 0; i < m.size(); ++i)
			ans.data_[i] = t / m.data_[i];
		return ans;
	}

	friend inline matrix&& operator /(matrix &&m, value_type t)
	{
		m /= t ;
		return std::move(m);
	}

	friend inline matrix&& operator /(value_type t, matrix&&m)
	{
		for(size_type i = 0; i < m.size(); ++i)
			m.data_[i] = t / m.data_[i];
		return std::move(m);
	}

private:
    size_type rows;
    size_type columns;
};
}
#endif 
